/*
 * Copyright (C) 2012 CyberAgent
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.jarlen.photoedit.filter.base;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.opengl.GLES20;

/**
 * Resembles a filter that consists of multiple filters applied after each
 * other.
 */
public class GPUImageFilterGroup extends GPUImageFilter
{

	protected List<GPUImageFilter> mFilters;
	protected List<GPUImageFilter> mMergedFilters;
	private int[] mFrameBuffers;
	private int[] mFrameBufferTextures;

	private final FloatBuffer mGLCubeBuffer;
	private final FloatBuffer mGLTextureBuffer;
	private final FloatBuffer mGLTextureFlipBuffer;

	/**
	 * Instantiates a new GPUImageFilterGroup with no filters.
	 */
	public GPUImageFilterGroup()
	{
		this(null);
	}

	/**
	 * Instantiates a new GPUImageFilterGroup with the given filters.
	 * 
	 * @param filters
	 *            the filters which represent this filter
	 */
	public GPUImageFilterGroup(List<GPUImageFilter> filters)
	{
		mFilters = filters;
		if (mFilters == null)
		{
			mFilters = new ArrayList<GPUImageFilter>();
		} else
		{
			updateMergedFilters();
		}

		mGLCubeBuffer = ByteBuffer
				.allocateDirect(GPUImageRenderer.CUBE.length * 4)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mGLCubeBuffer.put(GPUImageRenderer.CUBE).position(0);

		mGLTextureBuffer = ByteBuffer
				.allocateDirect(
						TextureRotationUtil.TEXTURE_NO_ROTATION.length * 4)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mGLTextureBuffer.put(TextureRotationUtil.TEXTURE_NO_ROTATION).position(
				0);

		float[] flipTexture = TextureRotationUtil.getRotation(Rotation.NORMAL,
				false, true);
		mGLTextureFlipBuffer = ByteBuffer
				.allocateDirect(flipTexture.length * 4)
				.order(ByteOrder.nativeOrder()).asFloatBuffer();
		mGLTextureFlipBuffer.put(flipTexture).position(0);
	}

	public void addFilter(GPUImageFilter aFilter)
	{
		if (aFilter == null)
		{
			return;
		}
		mFilters.add(aFilter);
		updateMergedFilters();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onInit()
	 */
	@Override
	public void onInit()
	{
		super.onInit();
		for (GPUImageFilter filter : mFilters)
		{
			filter.init();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onDestroy()
	 */
	@Override
	public void onDestroy()
	{
		destroyFramebuffers();
		for (GPUImageFilter filter : mFilters)
		{
			filter.destroy();
		}
		super.onDestroy();
	}

	private void destroyFramebuffers()
	{
		if (mFrameBufferTextures != null)
		{
			GLES20.glDeleteTextures(mFrameBufferTextures.length,
					mFrameBufferTextures, 0);
			mFrameBufferTextures = null;
		}
		if (mFrameBuffers != null)
		{
			GLES20.glDeleteFramebuffers(mFrameBuffers.length, mFrameBuffers, 0);
			mFrameBuffers = null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * jp.co.cyberagent.android.gpuimage.GPUImageFilter#onOutputSizeChanged(int,
	 * int)
	 */
	@Override
	public void onOutputSizeChanged(final int width, final int height)
	{
		super.onOutputSizeChanged(width, height);
		if (mFrameBuffers != null)
		{
			destroyFramebuffers();
		}

		int size = mFilters.size();
		for (int i = 0; i < size; i++)
		{
			mFilters.get(i).onOutputSizeChanged(width, height);
		}

		if (mMergedFilters != null && mMergedFilters.size() > 0)
		{
			size = mMergedFilters.size();
			mFrameBuffers = new int[size - 1];
			mFrameBufferTextures = new int[size - 1];

			for (int i = 0; i < size - 1; i++)
			{
				GLES20.glGenFramebuffers(1, mFrameBuffers, i);
				GLES20.glGenTextures(1, mFrameBufferTextures, i);
				GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,
						mFrameBufferTextures[i]);
				GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA,
						width, height, 0, GLES20.GL_RGBA,
						GLES20.GL_UNSIGNED_BYTE, null);
				GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
						GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
				GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
						GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
				GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
						GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
				GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
						GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

				GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,
						mFrameBuffers[i]);
				GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,
						GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D,
						mFrameBufferTextures[i], 0);

				GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
				GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see jp.co.cyberagent.android.gpuimage.GPUImageFilter#onDraw(int,
	 * java.nio.FloatBuffer, java.nio.FloatBuffer)
	 */
	@SuppressLint("WrongCall")
	@Override
	public void onDraw(final int textureId, final FloatBuffer cubeBuffer,
			final FloatBuffer textureBuffer)
	{
		runPendingOnDrawTasks();
		if (!isInitialized() || mFrameBuffers == null
				|| mFrameBufferTextures == null)
		{
			return;
		}
		if (mMergedFilters != null)
		{
			int size = mMergedFilters.size();
			int previousTexture = textureId;
			for (int i = 0; i < size; i++)
			{
				GPUImageFilter filter = mMergedFilters.get(i);
				boolean isNotLast = i < size - 1;
				if (isNotLast)
				{
					GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER,
							mFrameBuffers[i]);
					GLES20.glClearColor(0, 0, 0, 0);
				}

				if (i == 0)
				{
					filter.onDraw(previousTexture, cubeBuffer, textureBuffer);
				} else if (i == size - 1)
				{
					filter.onDraw(previousTexture, mGLCubeBuffer,
							(size % 2 == 0)
									? mGLTextureFlipBuffer
									: mGLTextureBuffer);
				} else
				{
					filter.onDraw(previousTexture, mGLCubeBuffer,
							mGLTextureBuffer);
				}

				if (isNotLast)
				{
					GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
					previousTexture = mFrameBufferTextures[i];
				}
			}
		}
	}

	/**
	 * Gets the filters.
	 * 
	 * @return the filters
	 */
	public List<GPUImageFilter> getFilters()
	{
		return mFilters;
	}

	public List<GPUImageFilter> getMergedFilters()
	{
		return mMergedFilters;
	}

	public void updateMergedFilters()
	{
		if (mFilters == null)
		{
			return;
		}

		if (mMergedFilters == null)
		{
			mMergedFilters = new ArrayList<GPUImageFilter>();
		} else
		{
			mMergedFilters.clear();
		}

		List<GPUImageFilter> filters;
		for (GPUImageFilter filter : mFilters)
		{
			if (filter instanceof GPUImageFilterGroup)
			{
				((GPUImageFilterGroup) filter).updateMergedFilters();
				filters = ((GPUImageFilterGroup) filter).getMergedFilters();
				if (filters == null || filters.isEmpty())
					continue;
				mMergedFilters.addAll(filters);
				continue;
			}
			mMergedFilters.add(filter);
		}
	}
}
